﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using Excel = Microsoft.Office.Interop.Excel;
using System.Threading;
using System.Diagnostics;
using System.Collections;
using System.Xml;
using System.IO;

namespace SmartContentUI.Util
{
    public partial class ExportDialog : Form
    {
        #region Delegates

        delegate void ExportDialog_SetTitleCallback(string text);
        delegate void lblState_SetTextCallback(string text);
        delegate void lblState_SetForeColorCallback(Color color);
        delegate void prgbarSaving_SetValueCallback(int value);
        delegate void btnClose_SetFocusCallback();
        delegate void btnClose_SetEnabledCallback(bool val);
        delegate void btnOpenDir_SetEnabledCallback(bool val);
        delegate void btnOpenDoc_SetEnabledCallback(bool val);
        delegate int column_GetLengthCallback();
        delegate string doc_GetItemCallback(int item);
        delegate bool doc_SetRowCallback();
        delegate string column_GetTypeCallback(int index);

        #endregion

        #region Constants

        private const int EXPORT_EXCEL = 1;
        private const int EXPORT_XML = 2;
        private const int EXPORT_TXT = 3;

        #endregion

        #region Variables

        private int typeExport;// Tipo de exportación: 1- Excel, 2- XML
        private string path;
        private string fileName;

        private SmartContentDB.Interfaces.Field[] column;
        private SmartContentDB.Interfaces.Document doc;
        private SmartContentBusiness.Session session;

        private Excel.Application excelApp;
        private Excel.Workbooks excelBooks;
        private Excel._Workbook m_objBook;
        private Excel.Sheets m_objSheets;
        private Excel._Worksheet m_objSheet;

        private XmlTextWriter myXmlTextWriter;
        private String[] textColumns;

        private TextWriter myTextWriter;

        #endregion

        #region Constructor

        public ExportDialog(int tExport, SmartContentDB.Interfaces.Field[] clm, SmartContentDB.Interfaces.Document dc, SmartContentBusiness.Session ses, String fn, String pth)
        {
            this.typeExport = tExport;
            this.fileName = fn;
            this.path = pth;
            this.column = clm;
            this.doc = dc;
            this.session = ses;

            InitializeComponent();
        }
        #endregion

        #region Functions

        private void saveFile()
        {
            this.ExportDialog_SetTitle("Guardando Documento " + this.fileName);
            this.prgbarSaving_SetValue(0);
            this.lblState_SetText("Progreso: 0%");

            try
            {
                switch(this.typeExport){
                    case EXPORT_EXCEL:
                        this.exportToExcel();
                        break;
                    case EXPORT_XML:
                        this.exportToXML();
                        break;
                    case EXPORT_TXT:
                        this.exportToTXT();
                        break;
                }
            }
            catch (Exception e) {
                MessageBox.Show("Ocurrió un problema al exportar el documento. Error: " + e.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
                this.btnClose_SetEnabled(true);
                this.prgbarSaving_SetValue(0);
                this.lblState_SetText("Error al exportar el documento.");
                this.lblState_SetForeColor(Color.Red);
                this.btnClose_SetFocus();
                return;
            }

            if(this.typeExport == EXPORT_EXCEL)
                this.releaseObjects();
            
            this.prgbarSaving_SetValue(100);
            this.lblState_SetText("Progreso: 100%. El proceso ha finalizado.");
            this.btnClose_SetEnabled(true);
            this.btnOpenDir_SetEnabled(true);
            this.btnOpenDoc_SetEnabled(true);
            this.btnClose_SetFocus();
        }

        private void exportToExcel() {
            this.createExcelApp();
            this.prgbarSaving_SetValue(25);
            this.lblState_SetText("Progreso: 25%");
            this.writeExcelHeader();
            this.prgbarSaving_SetValue(50);
            this.lblState_SetText("Progreso: 50%");
            this.writeExcelContent();
            this.prgbarSaving_SetValue(75);
            this.lblState_SetText("Progreso: 75%");
            this.saveAndCloseExcel();
        }

        private void exportToXML() {
            this.setXMLWriter();
            this.prgbarSaving_SetValue(33);
            this.lblState_SetText("Progreso: 33%");
            this.writeXMLContent();
            this.prgbarSaving_SetValue(66);
            this.lblState_SetText("Progreso: 66%");
            this.closeXMLWriter();
        }

        private void exportToTXT() {
            this.setTXTWriter();
            this.prgbarSaving_SetValue(25);
            this.lblState_SetText("Progreso: 25%");
            this.writeTXTHeader();
            this.prgbarSaving_SetValue(50);
            this.lblState_SetText("Progreso: 50%");
            this.writeTXTContent();
            this.prgbarSaving_SetValue(75);
            this.lblState_SetText("Progreso: 75%");
            this.closeTXTWriter();
        }

        private void createExcelApp() {
            this.excelApp = new Excel.Application();
            this.excelBooks = (Excel.Workbooks)this.excelApp.Workbooks;
            this.m_objBook = (Excel._Workbook)(this.excelBooks.Add(Missing.Value));
            this.m_objSheets = (Excel.Sheets)this.m_objBook.Worksheets;
            this.m_objSheet = (Excel._Worksheet)(this.m_objSheets.get_Item(1));
        }

        private void writeExcelHeader()
        {
            object[,] objData = new Object[1, this.column.Length];

            for (int i = 0; i < this.column.Length; i++)
            {
                objData[0, i] = this.column[i].getName();
            }

            Excel.Range m_objRange = m_objSheet.get_Range("A1", Missing.Value);
            m_objRange = m_objRange.get_Resize(1, this.column.Length);
            m_objRange.Value2 = objData;
            Excel.Font m_objFont = m_objRange.Font;
            m_objFont.Bold = true;
        }

        private void setXMLWriter() {
            this.myXmlTextWriter = new XmlTextWriter(this.path + "\\" + this.fileName, System.Text.Encoding.UTF8);
            this.myXmlTextWriter.Formatting = Formatting.Indented;
            this.myXmlTextWriter.WriteStartDocument(false);
            this.textColumns = new String[this.column.Length];
            for (int i = 0; i < this.column.Length; i++)
            {
                this.textColumns[i] = this.column[i].getName().Replace(" ", "_").ToString();
            }
        }

        private void setTXTWriter()
        {
            this.myTextWriter = new StreamWriter(this.path + "\\" + this.fileName);
        }

        private void writeTXTHeader() {
            for (int i = 0; i < this.column.Length; i++)
            {
                this.myTextWriter.Write(" " + this.column[i].getName() + " |");
            }
            this.myTextWriter.WriteLine("");
        }

        private void writeExcelContent()
        {
            ArrayList list = new ArrayList();
            int fields = this.column_GetLength();
            object[,] objData;

            object[] row = new object[fields];

            for (int i = 0; i < fields; i++)
            {
                row[i] = this.doc_GetItem(i);
            }

            list.Add(row);
            
            while (this.doc_SetRow()) {
                row = new object[fields];
                for (int j = 0; j < fields; j++)
                {
                    row[j] = this.doc_GetItem(j);
                }
                list.Add(row);
            }

            objData = this.buildAndGetObject(list, fields);

            Excel.Range m_objRange = m_objSheet.get_Range("A2", Missing.Value);
            m_objRange = m_objRange.get_Resize(list.Count, fields);
            m_objRange.Value2 = objData;

            objData = null;
            list.Clear();
            list = null;
            System.GC.Collect();
        }

        private void writeXMLContent() {
            int fields = this.column_GetLength();

            this.myXmlTextWriter.WriteStartElement("Expediente");
            this.myXmlTextWriter.WriteStartElement("Documento");

            for (int i = 0; i < fields; i++)
            {
                this.myXmlTextWriter.WriteElementString(this.textColumns[i], this.doc_GetItem(i));
            }

            this.myXmlTextWriter.WriteEndElement();

            while (this.doc_SetRow())
            {
                this.myXmlTextWriter.WriteStartElement("Documento");

                for (int j = 0; j < fields; j++)
                {
                    this.myXmlTextWriter.WriteElementString(this.textColumns[j], this.doc_GetItem(j));
                }

                this.myXmlTextWriter.WriteEndElement();
            }

            this.myXmlTextWriter.WriteEndElement();
        }

        private void writeTXTContent()
        {
            int fields = this.column_GetLength();

            for (int i = 0; i < fields; i++)
            {
                this.myTextWriter.Write(" " + this.doc_GetItem(i) + " |");
            }
            this.myTextWriter.WriteLine("");

            while (this.doc_SetRow())
            {
                for (int j = 0; j < fields; j++)
                {
                    this.myTextWriter.Write(" " + this.doc_GetItem(j) + " |");
                }
                this.myTextWriter.WriteLine("");
            }
        }

        private void saveAndCloseExcel()
        {
            this.excelApp.DisplayAlerts = false;//Para que no pregunte nuevamente si desea sobreescribir el archivo en caso de que el archivo ya exista.
            this.m_objBook.SaveAs(this.path + "\\" + this.fileName, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Excel.XlSaveAsAccessMode.xlNoChange, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value);
            this.excelApp.DisplayAlerts = true;
            this.m_objBook.Close(false, Missing.Value, Missing.Value);
            this.excelApp.Quit();
        }

        private void closeXMLWriter() {
            this.myXmlTextWriter.Flush();
            this.myXmlTextWriter.Close();
        }

        private void closeTXTWriter()
        {
            this.myTextWriter.Close();
        }

        private void releaseObjects() {
            this.releaseObject(this.m_objSheet);
            this.releaseObject(this.m_objSheets);
            this.releaseObject(this.m_objBook);
            this.releaseObject(this.excelBooks);
            this.releaseObject(this.excelApp);
        }

        private void releaseObject(object obj)
        {
            try
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
                obj = null;
            }
            catch (Exception ex)
            {
                obj = null;
                MessageBox.Show("Ocurrió un problema al liberar el objeto. Error: " + ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
            }
            finally
            {
                GC.Collect();
            }
        }

        private object[,] buildAndGetObject(ArrayList list, int fields) {
 
            object[,] objData = new object[list.Count, fields];
            object[] element;
            int i = 0;

            System.Collections.IEnumerator enumerator = list.GetEnumerator();
            while (enumerator.MoveNext())
            {
                element = (object[])enumerator.Current;
                for (int j = 0; j < fields; j++)
                {
                    if (this.column_GetType(j).ToUpper().Equals("TINYINT"))
                    {
                        if (element[j].Equals("1") || element[j].ToString().ToLower().Equals("true"))
                            objData[i, j] = "1";
                        else
                            objData[i, j] = "0";
                    }
                    else if (this.column_GetType(j).ToUpper().Equals("DATETIME"))
                    {
                        objData[i, j] = String.Format("{0:MM/dd/yyyy}", element[j]) + ".";//Concateno con un punto al final para que en excel no se guarde como un dato de tipo double. Concateno para romper el formato y así que lo tome como un string cualquiera.
                    }
                    else {
                        objData[i, j] = element[j];
                    }
                }
                i++;
            }

            return objData;
        }

        #endregion

        #region Thread-Safe Call Functions

        private void ExportDialog_SetTitle(string text) {
            if (this.InvokeRequired)
            {
                ExportDialog_SetTitleCallback d = new ExportDialog_SetTitleCallback(this.ExportDialog_SetTitle);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.Text = text;
            }
        }

        private void lblState_SetText(string text) {
            if (this.InvokeRequired)
            {
                lblState_SetTextCallback d = new lblState_SetTextCallback(this.lblState_SetText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.lblState.Text = text;
            }
        }

        private void lblState_SetForeColor(Color color)
        {
            if (this.InvokeRequired)
            {
                lblState_SetForeColorCallback d = new lblState_SetForeColorCallback(this.lblState_SetForeColor);
                this.Invoke(d, new object[] { color });
            }
            else
            {
                this.lblState.ForeColor = color;
            }
        }

        private void prgbarSaving_SetValue(int value) {
            if (this.InvokeRequired)
            {
                prgbarSaving_SetValueCallback d = new prgbarSaving_SetValueCallback(prgbarSaving_SetValue);
                this.Invoke(d, new object[] { value });
            }
            else
            {
                this.prgbarSaving.Value = value;
            }
        }

        private void btnClose_SetFocus() {
            if (this.InvokeRequired)
            {
                btnClose_SetFocusCallback d = new btnClose_SetFocusCallback(this.btnClose_SetFocus);
                this.Invoke(d);
            }
            else
            {
                this.btnClose.Focus();
            }
        }

        private void btnClose_SetEnabled(bool val) {
            if (this.InvokeRequired)
            {
                btnClose_SetEnabledCallback d = new btnClose_SetEnabledCallback(this.btnClose_SetEnabled);
                this.Invoke(d, new object[] { val });
            }
            else
            {
                this.btnClose.Enabled = val;
            }
        }

        private void btnOpenDir_SetEnabled(bool val)
        {
            if (this.InvokeRequired)
            {
                btnOpenDir_SetEnabledCallback d = new btnOpenDir_SetEnabledCallback(this.btnOpenDir_SetEnabled);
                this.Invoke(d, new object[] { val });
            }
            else
            {
                this.btnOpenDir.Enabled = val;
            }
        }

        private void btnOpenDoc_SetEnabled(bool val)
        {
            if (this.InvokeRequired)
            {
                btnOpenDoc_SetEnabledCallback d = new btnOpenDoc_SetEnabledCallback(this.btnOpenDoc_SetEnabled);
                this.Invoke(d, new object[] { val });
            }
            else
            {
                this.btnOpenDoc.Enabled = val;
            }
        }

        private int column_GetLength()
        {
            if (this.InvokeRequired)
            {
                column_GetLengthCallback d = new column_GetLengthCallback(this.column_GetLength);
                return (int)this.Invoke(d);
            }
            else
            {
                return this.column.Length;
            }
        }

        private string doc_GetItem(int item)
        {
            if (this.InvokeRequired)
            {
                doc_GetItemCallback d = new doc_GetItemCallback(this.doc_GetItem);
                return this.Invoke(d, new object[] { item }).ToString();
            }
            else
            {
                return this.doc.Fields[item];
            }
        }

        private bool doc_SetRow()
        {
            if (this.InvokeRequired)
            {
                doc_SetRowCallback d = new doc_SetRowCallback(this.doc_SetRow);
                return (bool)this.Invoke(d);
            }
            else
            {
                return (this.doc = this.session.getRow(this.column)) != null;
            }
        }

        private string column_GetType(int index)
        {
            if (this.InvokeRequired)
            {
                column_GetTypeCallback d = new column_GetTypeCallback(this.column_GetType);
                return this.Invoke(d, new object[] { index }).ToString();
            }
            else
            {
                return this.column[index].getType();
            }
        }

        #endregion

        #region Events

        private void ExportDialog_Load(object sender, EventArgs e)
        {
            Thread threadExport = new Thread(new ThreadStart(this.saveFile));
            threadExport.Start();
        }

        private void btnClose_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void btnOpenDoc_Click(object sender, EventArgs e)
        {
            ProcessStartInfo startInfo;

            try
            {
                switch(this.typeExport){
                    case EXPORT_EXCEL:
                        startInfo = new ProcessStartInfo();
                        startInfo.FileName = "EXCEL.EXE"; 
                        startInfo.Arguments = "\"" + this.path + "\\" + this.fileName + "\"";
                        Process.Start(startInfo);        
                        break;
                    case EXPORT_XML:
                        string target = "\"" + this.path + "\\" + this.fileName + "\"";
                        Process.Start(target);
                        break;
                    case EXPORT_TXT:
                        startInfo = new ProcessStartInfo();
                        startInfo.FileName = "NOTEPAD.EXE";
                        startInfo.Arguments = "\"" + this.path + "\\" + this.fileName + "\"";
                        Process.Start(startInfo);  
                        break;
                }                
            }catch(Exception ex){
                MessageBox.Show("Ocurrió un problema al abrir el documento. Error: " + ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
            }
        }

        private void btnOpenDir_Click(object sender, EventArgs e)
        {
            try{
                Process.Start("explorer.exe", @"" + this.path);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Ocurrió un problema al abrir el directorio. Error: " + ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
            }
        }

        #endregion        
        
    }

}
